home *** CD-ROM | disk | FTP | other *** search
/ NetNews Offline 2 / NetNews Offline Volume 2.iso / news / comp / lang / c++-part1 / 8938 < prev    next >
Encoding:
Text File  |  1996-08-05  |  10.5 KB  |  320 lines

  1. Path: news.wwa.com!rmartin
  2. From: rmartin@oma.com (Robert C. Martin)
  3. Newsgroups: comp.object,comp.lang.c++
  4. Subject: Re: A design question
  5. Followup-To: comp.object,comp.lang.c++
  6. Date: 27 Feb 1996 15:00:05 GMT
  7. Organization: Object Mentor
  8. Message-ID: <RMARTIN.96Feb27090005@rcm.oma.com>
  9. References: <1996Feb22.234825.18755@dcs.warwick.ac.uk> <4gu4br$bmc@news4.digex.net>
  10. NNTP-Posting-Host: rcm.oma.com
  11. In-reply-to: ell@access1.digex.net's message of 27 Feb 1996 05:20:59 GMT
  12.  
  13. In article <4gu4br$bmc@news4.digex.net> ell@access1.digex.net (Ell) writes:
  14.  
  15.    Robert C. Martin (rmartin@oma.com) wrote:
  16.    : Indeed.  And there is a very nice pattern in the "Design Patterns"
  17.    : book that describes the way in which a state machine can be
  18.    : implemented to do just what Miroslav is asking.  The pattern is called
  19.    : "State".  
  20.    : 
  21.    : Consider the following:
  22.    : 
  23.    : class MyParcel;  /fwd declare.
  24.    : 
  25.    : // this class is the abstract representation of the state of the
  26.    : // parcel of data.  It defines functions which respond to all the
  27.    : // events that a parcel may experience.
  28.    : 
  29.    : class ParcelState
  30.    : {
  31.    :   public:
  32.    :     // the events that occur along the processing chain.
  33.    :     virtual void Event1(MyParcel&) = 0;
  34.    :     virtual void Event2(MyParcel&) = 0;
  35.    : };
  36.    : 
  37.    : // This is the parcel itself.  It also defines functions which respond
  38.    : // to all its events.  However, these functions simply delegate to the
  39.    : // contained state object.
  40.    : 
  41.    : class MyParcel
  42.    : {
  43.    :   public:
  44.    :     MyParcel();
  45.    :     void Event1() {itsState->Event1(*this);}
  46.    :     void Event2() {itsState->Event2(*this);}
  47.    :   private:
  48.    :     ParcelState* itsState;
  49.    : };
  50.  
  51.    I'm wondering if the above class MyParcel shouldn't publicly inherit from 
  52.    ParcelState?
  53.  
  54. Actually the story is not complete.  Once told it will be clear why
  55. deriving MyParcel from Parcel state is not a good idea. 
  56.  
  57. Let's change the names to make things clearer.
  58.  
  59. Assume that we have a subway turnstyle.  The class that represents
  60. this turnstyle has several public member functions:
  61.  
  62. class Turnstyle
  63. {
  64.   public:
  65.     void Lock();
  66.     void Unlock();
  67.     void Refund();
  68.     void Alarm();
  69. };
  70.  
  71. These are all the operations that the turnstyle can perform.  We want
  72. to create the logic that will cause the appropriate operation to be
  73. invoked in response to an event.  There are two possible events:  
  74.  
  75.     Coin  -  The user drops a coin or subway token into the slot.
  76.         Pass  -  The user passes through the turnstyle.
  77.  
  78. We can create a state table that describes the operation of the
  79. turnstyle.
  80.  
  81.     Current State   Event  New State   Action
  82.       LOCKED        COIN    UNLOCKED    Unlock
  83.       LOCKED        PASS    LOCKED      Alarm
  84.       UNLOCKED      COIN    UNLOCKED    Refund
  85.       UNLOCKED      PASS    LOCKED      Lock
  86.  
  87. Fine.  Now we can create a class that derives from Turnstyle and
  88. implements this FSM.  Note the class below: simpleTurnstyle
  89.  
  90. ---------------simpleTurnstyle.h (generated by smc) ---------------
  91. #ifndef _H_SimpleTurnstyle
  92. #define _H_SimpleTurnstyle
  93. #include <stddef.h>
  94. #include "turnstyle.h"
  95. class SimpleTurnstyle;
  96.  
  97. class SimpleTurnstyleState {
  98. public:
  99.  
  100.   virtual const char* StateName() const = 0;
  101.   virtual void PASS(SimpleTurnstyle& s);
  102.   virtual void COIN(SimpleTurnstyle& s);
  103. };
  104.  
  105. class SimpleTurnstyleUNLOCKEDState : public SimpleTurnstyleState {
  106. public:
  107.   virtual const char* StateName() const
  108.   {return("UNLOCKED");};
  109.   virtual void PASS(SimpleTurnstyle&);
  110.   virtual void COIN(SimpleTurnstyle&);
  111. };
  112.  
  113. class SimpleTurnstyleLOCKEDState : public SimpleTurnstyleState {
  114. public:
  115.   virtual const char* StateName() const
  116.   {return("LOCKED");};
  117.   virtual void PASS(SimpleTurnstyle&);
  118.   virtual void COIN(SimpleTurnstyle&);
  119. };
  120. class SimpleTurnstyle : public Turnstyle {
  121.   public:
  122.   static SimpleTurnstyleUNLOCKEDState UNLOCKEDState;
  123.   static SimpleTurnstyleLOCKEDState LOCKEDState;
  124.   SimpleTurnstyle();// default constructor
  125.   void PASS() {itsState->PASS(*this);}
  126.   void COIN() {itsState->COIN(*this);}
  127.   void SetState(SimpleTurnstyleState& theState) {itsState=&theState;}
  128.   SimpleTurnstyleState& GetState() const {return *itsState;};
  129.   private:
  130.     SimpleTurnstyleState* itsState;
  131. };
  132. #endif
  133.  
  134. -------------simpleTurnstyle.cc------------------
  135.  
  136.  
  137. #include "simpleTurnstyle.h"
  138. static char _versID[] = "No Version.";
  139. SimpleTurnstyleUNLOCKEDState SimpleTurnstyle::UNLOCKEDState;
  140. SimpleTurnstyleLOCKEDState SimpleTurnstyle::LOCKEDState;
  141. void SimpleTurnstyleState::PASS(SimpleTurnstyle& s)
  142.   {s.FSMError("PASS", s.GetState().StateName());}
  143. void SimpleTurnstyleState::COIN(SimpleTurnstyle& s)
  144.   {s.FSMError("COIN", s.GetState().StateName());}
  145. void SimpleTurnstyleUNLOCKEDState::PASS(SimpleTurnstyle& s) {
  146.   s.SetState(SimpleTurnstyle::LOCKEDState);
  147.   s.Lock();
  148. }
  149. void SimpleTurnstyleUNLOCKEDState::COIN(SimpleTurnstyle& s) {
  150.   s.SetState(SimpleTurnstyle::UNLOCKEDState);
  151.   s.Refund();
  152. }
  153. void SimpleTurnstyleLOCKEDState::PASS(SimpleTurnstyle& s) {
  154.   s.SetState(SimpleTurnstyle::LOCKEDState);
  155.   s.Alarm();
  156. }
  157. void SimpleTurnstyleLOCKEDState::COIN(SimpleTurnstyle& s) {
  158.   s.SetState(SimpleTurnstyle::UNLOCKEDState);
  159.   s.Unlock();
  160. }
  161. SimpleTurnstyle::SimpleTurnstyle() : itsState(&LOCKEDState) {}
  162.  
  163. -----------------------------------------------------------
  164.  
  165. OK, now it looks like we could have simply put the PASS and COIN
  166. events into Turnstyle and then had a common base for Turnstyle and
  167. simpleTurnstyleState.  However, consider this:
  168.  
  169.  
  170.       LOCKED        COIN    HALFPAID    {}
  171.       LOCKED        PASS    VIOLATED    {Alarm Lock}
  172.       HALFPAID      COIN    UNLOCKED    Unlock
  173.       HALFPAID      PASS    VIOLATED    {Alarm Lock}
  174.       UNLOCKED      COIN    UNLOCKED    Refund
  175.       UNLOCKED      PASS    LOCKED      Lock
  176.       VIOLATED      COIN    VIOLATED    Refund
  177.       VIOLATED      PASS    VIOLATED    {Alarm Lock}
  178.       VIOLATED      RESET   LOCKED      {}
  179.  
  180. Here is some turnstyle logic that is much more complicated than the
  181. previous one.  This logic requires TWO coins before unlocking.
  182. Moreover, once violated, it refuses to operate until a maintenance
  183. worker issues the RESET event.
  184.  
  185. The code for this TwoCoinTurnstyle is shown below.  Note that the
  186. TwoCoinTurnstyle class derives from Turnstyle.  
  187.  
  188. -------------------twoCoinTurnstyle.h (generated by smc) --------
  189.  
  190. #ifndef _H_TwoCoinTurnstyle
  191. #define _H_TwoCoinTurnstyle
  192. #include <stddef.h>
  193. #include "turnstyle.h"
  194. class TwoCoinTurnstyle;
  195.  
  196. class TwoCoinTurnstyleState {
  197. public:
  198.  
  199.   virtual const char* StateName() const = 0;
  200.   virtual void RESET(TwoCoinTurnstyle& s);
  201.   virtual void PASS(TwoCoinTurnstyle& s);
  202.   virtual void COIN(TwoCoinTurnstyle& s);
  203. };
  204.  
  205. class TwoCoinTurnstyleVIOLATEDState : public TwoCoinTurnstyleState {
  206. public:
  207.   virtual const char* StateName() const
  208.   {return("VIOLATED");};
  209.   virtual void RESET(TwoCoinTurnstyle&);
  210.   virtual void PASS(TwoCoinTurnstyle&);
  211.   virtual void COIN(TwoCoinTurnstyle&);
  212. };
  213.  
  214. class TwoCoinTurnstyleUNLOCKEDState : public TwoCoinTurnstyleState {
  215. public:
  216.   virtual const char* StateName() const
  217.   {return("UNLOCKED");};
  218.   virtual void PASS(TwoCoinTurnstyle&);
  219.   virtual void COIN(TwoCoinTurnstyle&);
  220. };
  221.  
  222. class TwoCoinTurnstyleHALFPAIDState : public TwoCoinTurnstyleState {
  223. public:
  224.   virtual const char* StateName() const
  225.   {return("HALFPAID");};
  226.   virtual void PASS(TwoCoinTurnstyle&);
  227.   virtual void COIN(TwoCoinTurnstyle&);
  228. };
  229.  
  230. class TwoCoinTurnstyleLOCKEDState : public TwoCoinTurnstyleState {
  231. public:
  232.   virtual const char* StateName() const
  233.   {return("LOCKED");};
  234.   virtual void PASS(TwoCoinTurnstyle&);
  235.   virtual void COIN(TwoCoinTurnstyle&);
  236. };
  237. class TwoCoinTurnstyle : public Turnstyle {
  238.   public:
  239.   static TwoCoinTurnstyleUNLOCKEDState UNLOCKEDState;
  240.   static TwoCoinTurnstyleVIOLATEDState VIOLATEDState;
  241.   static TwoCoinTurnstyleHALFPAIDState HALFPAIDState;
  242.   static TwoCoinTurnstyleLOCKEDState LOCKEDState;
  243.   TwoCoinTurnstyle();// default constructor
  244.   void RESET() {itsState->RESET(*this);}
  245.   void PASS() {itsState->PASS(*this);}
  246.   void COIN() {itsState->COIN(*this);}
  247.   void SetState(TwoCoinTurnstyleState& theState) {itsState=&theState;}
  248.   TwoCoinTurnstyleState& GetState() const {return *itsState;};
  249.   private:
  250.     TwoCoinTurnstyleState* itsState;
  251. };
  252. #endif
  253.  
  254. -------------------twoCoinTurnstyle.cc (generated by smc) -----
  255.  
  256. #include "twoCoinTurnstyle.h"
  257. static char _versID[] = "No Version.";
  258. TwoCoinTurnstyleUNLOCKEDState TwoCoinTurnstyle::UNLOCKEDState;
  259. TwoCoinTurnstyleVIOLATEDState TwoCoinTurnstyle::VIOLATEDState;
  260. TwoCoinTurnstyleHALFPAIDState TwoCoinTurnstyle::HALFPAIDState;
  261. TwoCoinTurnstyleLOCKEDState TwoCoinTurnstyle::LOCKEDState;
  262. void TwoCoinTurnstyleState::RESET(TwoCoinTurnstyle& s)
  263.   {s.FSMError("RESET", s.GetState().StateName());}
  264. void TwoCoinTurnstyleState::PASS(TwoCoinTurnstyle& s)
  265.   {s.FSMError("PASS", s.GetState().StateName());}
  266. void TwoCoinTurnstyleState::COIN(TwoCoinTurnstyle& s)
  267.   {s.FSMError("COIN", s.GetState().StateName());}
  268. void TwoCoinTurnstyleVIOLATEDState::RESET(TwoCoinTurnstyle& s) {
  269.   s.SetState(TwoCoinTurnstyle::LOCKEDState);
  270. }
  271. void TwoCoinTurnstyleVIOLATEDState::PASS(TwoCoinTurnstyle& s) {
  272.   s.SetState(TwoCoinTurnstyle::VIOLATEDState);
  273.   s.Alarm();
  274.   s.Lock();
  275. }
  276. void TwoCoinTurnstyleVIOLATEDState::COIN(TwoCoinTurnstyle& s) {
  277.   s.SetState(TwoCoinTurnstyle::VIOLATEDState);
  278.   s.Refund();
  279. }
  280. void TwoCoinTurnstyleUNLOCKEDState::PASS(TwoCoinTurnstyle& s) {
  281.   s.SetState(TwoCoinTurnstyle::LOCKEDState);
  282.   s.Lock();
  283. }
  284. void TwoCoinTurnstyleUNLOCKEDState::COIN(TwoCoinTurnstyle& s) {
  285.   s.SetState(TwoCoinTurnstyle::UNLOCKEDState);
  286.   s.Refund();
  287. }
  288. void TwoCoinTurnstyleHALFPAIDState::PASS(TwoCoinTurnstyle& s) {
  289.   s.SetState(TwoCoinTurnstyle::VIOLATEDState);
  290.   s.Alarm();
  291.   s.Lock();
  292. }
  293. void TwoCoinTurnstyleHALFPAIDState::COIN(TwoCoinTurnstyle& s) {
  294.   s.SetState(TwoCoinTurnstyle::UNLOCKEDState);
  295.   s.Unlock();
  296. }
  297. void TwoCoinTurnstyleLOCKEDState::PASS(TwoCoinTurnstyle& s) {
  298.   s.SetState(TwoCoinTurnstyle::VIOLATEDState);
  299.   s.Alarm();
  300.   s.Lock();
  301. }
  302. void TwoCoinTurnstyleLOCKEDState::COIN(TwoCoinTurnstyle& s) {
  303.   s.SetState(TwoCoinTurnstyle::HALFPAIDState);
  304. }
  305. TwoCoinTurnstyle::TwoCoinTurnstyle() : itsState(&LOCKEDState) {}
  306.  
  307. -------------------------------------------------------------------
  308.  
  309. From this you can see the reason that Turnstyle cannot derive from
  310. TurnstyleState.  We want to be able to reuse Turnstyle (the primitive
  311. operations) in many different finite state machines.  So we want to
  312. isolate the events of the FSM from the actions of the FSM.  
  313.  
  314. --
  315. Robert Martin       | Design Consulting   | Training courses offered:
  316. Object Mentor Assoc.| rmartin@oma.com     |   OOA/D, C++, Advanced OO
  317. 14619 N. Somerset Cr| Tel: (847) 918-1004 |   Mgt. Overview of OOT
  318. Green Oaks IL 60048 | Fax: (847) 918-1023 | Development Contracts.
  319.  
  320.